package wordit.ea;

import java.util.Arrays;
import java.util.Collection;

import wordit.impl.boardwise.Validator;

public class Generation {

	private final Genome[] currGeneration;
	private final int currGenNumber;
	private final FitnessFunction fitnessFunction;
	private final MutationFunction mutateFunction;
	private final CrossoverFunction crossoverFunction;
	private final SelectionFunction selectionFunction;

	/**
	 * Creates a new generation
	 * 
	 * @param generationSize
	 *            how many individuals are in this generation
	 * @param generationNumber
	 *            a sequential number for this generation
	 * @param conf
	 */
	public Generation(int generationSize, int generationNumber,
			Configuration conf) {
		currGeneration = new Genome[generationSize];
		currGenNumber = generationNumber;
		this.fitnessFunction = conf.getFitnessFunction();
		this.mutateFunction = conf.getMutationFunction();
		this.crossoverFunction = conf.getCrossoverFunction();
		this.selectionFunction = conf.getSelectionFunction();

		if (fitnessFunction == null)
			throw new IllegalArgumentException("fitness function not set");
		if (mutateFunction == null)
			throw new IllegalArgumentException("mutation function not set");
		if (crossoverFunction == null)
			throw new IllegalArgumentException("crossover function not set");
		if (selectionFunction == null)
			throw new IllegalArgumentException("selection function not set");
	}

	public Generation(Generation other, int generationNumber) {
		this.currGenNumber = generationNumber;
		
		this.currGeneration = new Genome[other.currGeneration.length];
		
		this.crossoverFunction = other.crossoverFunction;
		this.fitnessFunction = other.fitnessFunction;
		this.mutateFunction = other.mutateFunction;
		this.selectionFunction = other.selectionFunction;
	}

	public int getCurrGenNumber() {
		return currGenNumber;
	}

	public Genome getBestGenome() {
		double bestGenomeFitness = Double.NEGATIVE_INFINITY;
		Genome bestGenome = null;
		for (Genome gen : currGeneration) {
			double currGenomeFitness = gen.getFitness();
			if (currGenomeFitness > bestGenomeFitness) {
				bestGenomeFitness = currGenomeFitness;
				bestGenome = gen;
			}
		}
		return bestGenome;
	}

	public double evalAverageFitness() {
		double totalFitness = 0;
		
		for (Genome g : currGeneration)
			totalFitness += g.getFitness();
		
		return totalFitness / (double)currGeneration.length;
	}

	/**
	 * Generates a new generation: crosses-over parents to create new
	 * offsprings, mutates them and evaluates them
	 * 
	 * @return a new generation
	 */
	public Generation nextGeneration() {
		Generation nextGen = crossoverGenomes();
		nextGen.mutateGenomes();
		nextGen.evaluateGenomes();
		return nextGen;
	}

	/**
	 * Selects parents and produces offsprings to form a new generation (of the
	 * same size)
	 * 
	 * @return a new generation (of the same size) containing new offsprings
	 */
	private Generation crossoverGenomes() {
		//Validator.validate(this); //TODO remove
		
		Generation newGen = new Generation(this,getCurrGenNumber()+1);

		int offspringNumber = 0;
		while (offspringNumber < getGenerationSize()) {
			Genome[] parents = selectionFunction.selectParents(currGeneration);
			Genome[] offsprings = crossoverFunction.crossGenoms(parents[0],
					parents[1]);

			newGen.setGenome(offspringNumber, offsprings[0]);
			offspringNumber++;
			newGen.setGenome(offspringNumber, offsprings[1]);
			offspringNumber++;
		}

		return newGen;
	}

	/**
	 * Mutates the genomes in this generation
	 */
	private void mutateGenomes() {
		for (int i = 0; i < getGenerationSize(); ++i) {
			setGenome(i, mutateFunction.mutateGenom(getGenome(i)));
		}
	}

	private Genome getGenome(int i) {
		return currGeneration[i];
	}

	/**
	 * Creates a new generation consisting of random genomes
	 * 
	 * @param generationSize
	 *            how many individuals are in the new generation
	 * @param conf
	 * @return a new generation
	 */
	public static Generation createFirstGeneration(Configuration conf,
			int generationSize) {
		Generation g0 = new Generation(generationSize, 0, conf);

		for (int i = 0; i < g0.getGenerationSize(); ++i) {
			g0.setGenome(i, conf.getGenomeFactory().createRandomGenome());
		}

		g0.evaluateGenomes();

		return g0;
	}

	/**
	 * Evaluates the fitness of the genomes in this generation
	 */
	private void evaluateGenomes() {
		for (Genome g : currGeneration) {
			g.setFitness(fitnessFunction.eval(g));
		}
	}

	private int getGenerationSize() {
		return currGeneration.length;
	}

	private void setGenome(int i, Genome g) {
		currGeneration[i] = g;
	}
	
	public String toString() {
		return "(Generation,"+getCurrGenNumber()+","+getGenerationSize()+")";
	}

	public Collection<Genome> getGenomes() {
		return Arrays.asList(currGeneration);
	}
}
